﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Resources;
using System.Collections.Specialized;

namespace GenericValidation
{
    /// <summary>
    /// Validator of <typeparamref name="TEntity"/> that contains a single validation logic, validation message(s) and tags.
    /// Validators may also depend on other validators of custom logic.
    /// </summary>
    /// <typeparam name="TEntity">The validated object type.</typeparam>
    public interface IValidator<TEntity>
        where TEntity:class
    {
        /// <summary>
        /// Gets the latest validation evaluation result.
        /// </summary>
        bool? IsValid { get; set; }

        /// <summary>
        /// Gets or sets the containing Validation container.
        /// </summary>
        IValidation Validation { get; set; }

        /// <summary>
        /// Adds the <paramref name="tag"/> as validation tag.
        /// </summary>
        /// <param name="tag">Tag to be added to the Validator.</param>
        /// <returns>IValidator&lt;<paramref name="TEntity"/>&gt;.</returns>
        IValidator<TEntity> Tag(string tag);

        /// <summary>
        /// Adds a validation message with <paramref name="format"/> template and <paramref name="args"/> parameters to the Validator.
        /// </summary>
        /// <param name="format">The validation message format string.</param>
        /// <param name="args">Validation message format parameters.</param>
        /// <returns>IValidator&lt;<typeparamref name="T"/>&gt;.</returns>
        IValidator<TEntity> Message(string format, params object[] args);

        /// <summary>
        /// Adds a validation message with <paramref name="format"/> template and <paramref name="args"/> Lambda Expression parameters to the Validator.
        /// The Lambda Expression args are evaluated at object validation evaluation, therefore they may contain any member values.
        /// </summary>
        /// <param name="format">The validation message format string.</param>
        /// <param name="args">Lamda Expressions (delegates) that are going to be validated at validation evaluation.
        /// This may include member values.
        /// </param>
        /// <returns>IValidator&lt;<typeparamref name="T"/>&gt;.</returns>
        IValidator<TEntity> Message(string format, params Expression<Func<TEntity, object>>[] args);

        /// <summary>
        /// Clears the validation messages list set for this Validator.
        /// </summary>
        /// <returns>IValidator&lt;<typeparamref name="T"/>&gt;.</returns>
        IValidator<TEntity> MessageClear();

        /// <summary>
        /// Gets the validation error messages for the <typeparamref name="TEntity"/> object.
        /// </summary>
        /// <param name="item"></param>
        /// <returns>The Array of validation messages.</returns>
        string[] GetErrorMessage(TEntity item);

        /// <summary>
        /// Evaluates the Validator against the <typeparamref name="TEntity"/> entity and the <paramref name="tags"/>.
        /// If the Validator has been tagged with one of the input <paramref name="tags"/> then the Validator is evaluated.
        /// </summary>
        /// <param name="item"><typeparamref name="TEntity"/> object</param>
        /// <param name="tags">The Tag list.</param>
        /// <returns>True, if the <paramref name="item"/> is valid.</returns>
        bool Eval(TEntity item, params string[] tags);

        /// <summary>
        /// Sets the validator evaluation dependency: the current Validator is evaluated only if the dependency lambda expression <paramref name="when"/> is evaluated to TRUE.
        /// There can be only one dependency (lambda expression or Validator).
        /// </summary>
        /// <param name="when">The evaluation dependency lambda expression.</param>
        /// <returns>IValidator&lt;<typeparamref name="T"/>&gt;.</returns>
        IValidator<TEntity> When(Expression<Func<TEntity, bool>> when);

        /// <summary>
        /// Sets the validator evaluation dependency: the current Validator is evaluated only if the dependency <paramref name="dependency"/> gets evaluated to TRUE.
        /// There can be only one dependency (lambda expression or Validator).
        /// </summary>
        /// <param name="dependency">Dependency Validator.</param>
        /// <returns>IValidator&lt;<typeparamref name="T"/>&gt;.</returns>
        IValidator<TEntity> When(IValidator<TEntity> dependency);
    }

    /// <summary>
    /// Standard Validator of <typeparamref name="TEntity"/> that contains a single validation custom logic, validation message(s) and tags.
    /// Validators may also depend on other validators of custom logic.
    /// </summary>
    /// <typeparam name="TEntity">The validated object type.</typeparam>
    public class Validator<T> : IValidator<T>
        where T : class
    {
        internal Validator(IValidation validation, Expression<Func<T, bool>> eval)
        {
            this.Validation = validation;
            this.Evaluate = eval;
            validation.Register(this);
        }

        public bool? IsValid { get; set; }

        public List<string> Tags = new List<string>();

        public Expression<Func<T, bool>> Evaluate { get; set; }

        public Expression<Func<T, bool>> ValidateWhen { get; set; }

        public Expression<Func<T, string>> OnErrorMessage { get; set; }

        public IValidation Validation { get; set; }

        internal List<ValidationMessage<T>> Messages = new List<ValidationMessage<T>>();

        #region IValidationRule<TEntity> Members

        public virtual IValidator<T> When(Expression<Func<T, bool>> when)
        {
            this.ValidateWhen = when;
            return this;
        }

        public virtual IValidator<T> When(IValidator<T> otherRule)
        {
            this.ValidateWhen = (p) => otherRule.IsValid.HasValue
                ? otherRule.IsValid.Value
                : otherRule.Eval(p, this.Tags.ToArray());

            return this;
        }

        public virtual bool Eval(T item, params string[] tags)
        {
            bool theResult = true;

            var tagged = true;

            if (tags != null && tags.Length > 0)
            {
                var query = from p in tags
                            where this.Tags.Contains(p)
                            select p;

                tagged = query.Count() != 0;
            }

            if (tagged)
            {
                Func<T, bool> when = (p) => true;

                if (this.ValidateWhen != null)
                {
                    when = this.ValidateWhen.Compile();
                }

                if (when((T)item))
                {
                    theResult = this.Evaluate.Compile()(item);
                }
            }

            this.IsValid = theResult;
            return theResult;
        }

        public string[] GetErrorMessage(T item)
        {
            var theResult = from p in this.Messages
                            select p.GetErrorMessage(item);

            return theResult.ToArray();
        }

        public virtual IValidator<T> Message(string format, params object[] args)
        {
            this.Messages.Add(
                new ValidationMessage<T>(this.Validation)
                {
                    ErrorTemplate = format,
                    Args = args
                });

            return this;
        }

        public virtual IValidator<T> Message(string format, params Expression<Func<T, object>>[] args)
        {
            this.Messages.Add(
                new ValidationMessage<T>(this.Validation)
                {
                    ErrorTemplate = format,
                    ErrorParams = args
                });

            return this;
        }

        public virtual IValidator<T> MessageClear()
        {
            this.Messages.Clear();
            return this;
        }

        public IValidator<T> Tag(string tag)
        {
            if (!this.Tags.Contains(tag))
            {
                this.Tags.Add(tag);
            }

            return this;
        }

        #endregion
    }
}
